#ifndef syntaxtree_h__
#define syntaxtree_h__

#include <iosfwd>
#include <utility>
#include "symbolstable.h"
#include "errors.h"


namespace CCompiler
{
	class SynExpression;
	typedef std::pair<SymbolType , SymbolType> BinOpTypePair;
	typedef std::pair<std::string, BinOpTypePair> BinOpOnTypes;
	typedef SymType* (*BinOpHandler)(SynExpression*&, SynExpression*&, SymTable &);  
	extern std::map<BinOpOnTypes, BinOpHandler> AvailableBinOpsAndHandlers;

	typedef std::pair<std::string, SymbolType> UnaryOpOnType;
	typedef SymType* (*UnaryOpHandler)(SynExpression*&, SymTable &);
	extern std::map<UnaryOpOnType, UnaryOpHandler> AvailableUnaryOpsAndHandlers;

    class SynNode
    {
    public:
        virtual void print(std::ostream &stream, int ident = 0) = 0;
        virtual ~SynNode() {}
    };

    class SynStatement: public SynNode
    {


    };

    class SynStatementBlock: public SynStatement
    {
    public:
        virtual void print(std::ostream &stream, int ident = 0) {}
    };

    class SynStatementExpression: public SynStatement
    {

    };

    class SynStatementFor: public SynStatement
    {

    };

    class SynStatementWhile: public SynStatement
    {

    };

    class SynStatementDoWhile: public SynStatement
    {

    };

    class SynStatementSwitch: public SynStatement
    {

    };                  


    class SynExpression: public SynNode
    {
    public:
        ~SynExpression() {}
        virtual SymType *getType(SymTable &tbl) = 0;
        virtual bool isLvalue() { return false; }
    };

    class SynConst: public SynExpression
    {
    };

    class SynVal: public SynExpression
    {
        std::string identifier_;
    public:
        SynVal(const std::string& identifier): identifier_(identifier) {}
        virtual SymType *getType(SymTable &tbl)
        {
            return tbl.find(identifier_)->getType();
        }
        const std::string &getName() { return identifier_; }
        virtual bool isLvalue() { return true; }
        void print(std::ostream &stream, int ident = 0);
    };

    class SynFunctionCall: public SynExpression
    {
        SynExpression *caller_;
        typedef std::list<SynExpression *> ArgList;
        ArgList arguements_;
    public:
        SynFunctionCall(SynExpression *caller): caller_(caller) {}
        void pushArgument(SynExpression *arg)
        {
            arguements_.push_back(arg);
        }
        void print(std::ostream &stream, int ident = 0);
        virtual SymType *getType(SymTable &tbl)
        {
            SymType *f = caller_->getType(tbl);
            if(f->getSymbolType() == stFunctionType)
                return ((SymFunctionType *)f)->getResultType()->getType();
            else
                return ((SymFunctionType *)((SymFunctionPointer *)f)->getPointed())->getResultType()->getType();
        }
        ~SynFunctionCall()
        {
            for(ArgList::iterator i = arguements_.begin();
                i != arguements_.end(); ++i)
                delete *i;
            delete caller_;
        }
    };

    class SynBinaryOp: public SynExpression
    {
    protected:
        SynExpression *first_;
        SynExpression *second_;
        std::string binOp_;
    public:
        SynBinaryOp(const std::string& binOp, SynExpression *f, SynExpression *s): 
          binOp_(binOp),
              first_(f),
              second_(s) {}
          virtual SymType *getType(SymTable &tbl)
          {
              SymType *ft = first_->getType(tbl)->getType(), *st = second_->getType(tbl)->getType();
              BinOpOnTypes boot(binOp_, BinOpTypePair(ft->getSymbolType(),
                  st->getSymbolType()));
              if(!AvailableBinOpsAndHandlers.count(boot))
                  throw Errors::SyntaxError("'" + binOp_ + '\'' + " undefined(left=" + ft->getName() + ",right=" +
                  st->getName() +')');
              return AvailableBinOpsAndHandlers[boot](first_, second_, tbl);
              //SymType *f = first_->getType(tbl), *s = second_->getType(tbl);
              //if(f == s) return f;
              //else if() some kind of don't know what to write :D
              // I need alcohol, lots of it
          }
          void print(std::ostream &stream, int ident = 0);
          ~SynBinaryOp() { delete first_; delete second_; }
    };

    class SynAssignOp: public SynBinaryOp
    {
    public:
        SynAssignOp(const std::string &assignOp, SynExpression *lval, SynExpression *right):
          SynBinaryOp(assignOp, lval, right) {}
        virtual SymType *getType(SymTable &tbl);
    };

    class SynDot: public SynBinaryOp
    {
    public:
        SynDot(SynExpression *f, SynExpression*s): SynBinaryOp(".", f, s) {}
        virtual SymType *getType(SymTable &tbl)
        {
            // need a protection here - thousands of it )
            return ((SymTypeStruct *)first_->getType(tbl))->getField(((SynVal *)second_)->getName())->getType();
        }
        virtual bool isLvalue() { return true; }
    };

    class SynArrow: public SynBinaryOp
    {
    public:
        SynArrow(SynExpression *f, SynExpression*s): SynBinaryOp("->", f, s) {}
        virtual SymType *getType(SymTable &tbl)
        {
            // need a protection here - thousands of it )
            return ((SymTypeStruct *)((SymTypePointer *)first_->getType(tbl))->getPointed())->
                getField(((SynVal *)second_)->getName())->getType();
        }
        virtual bool isLvalue() { return true; }
    };

    class SynUnaryOp: public SynExpression
    {
    protected:
        SynExpression *first_;
        std::string unOp_;
        bool postfix_;
    public:
        SynUnaryOp(const std::string& unOp, SynExpression *f, bool postfix = false):
          first_(f), unOp_(unOp), postfix_(postfix) {}
        virtual SymType *getType(SymTable &tbl) 
        { 
            UnaryOpOnType uoot(unOp_, first_->getType(tbl)->getSymbolType());
            if((unOp_ == "++" || unOp_ == "--" || unOp_ == "&") && !first_->isLvalue())
                throw Errors::SyntaxError("Operand of '" + unOp_ + "' must be lvalue");
            if(!AvailableUnaryOpsAndHandlers.count(uoot))
                throw Errors::SyntaxError("'" + unOp_ + '\'' + " undefined(operand=" + first_->getType(tbl)->getName() + ")");
            return AvailableUnaryOpsAndHandlers[uoot](first_, tbl);
        }
        virtual bool isLvalue() { return unOp_ == "*"; }
        void print(std::ostream &stream, int ident = 0);
        ~SynUnaryOp() { delete first_; }
    };
        
    class SynCast: public SynUnaryOp
    {
        SymType *toType_;
    public:
        SynCast(SymType *toType, SynExpression *expr): 
		  SynUnaryOp('(' + toType->getName() + ')', expr), toType_(toType) {}
        virtual SymType *getType(SymTable &tbl) { return toType_; } 
    };

    class SynInt: public SynConst
    {
        int value_;
    public:
        SynInt(int value): value_(value) {}
        virtual SymType *getType(SymTable &tbl)
        {
            return tbl.find("int")->getType();
        }
        void print(std::ostream &stream, int ident = 0);
    };

    class SynChar: public SynConst
    {
        char value_;
    public:
        SynChar(char value): value_(value) {}
        virtual SymType *getType(SymTable &tbl)
        {
            return tbl.find("char")->getType();
        }
        void print(std::ostream &stream, int ident = 0);
    };

    class SynFloat: public SynConst
    {
        float value_;
    public:
        SynFloat(float value): value_(value) {}
        virtual SymType *getType(SymTable &tbl)
        {
            return tbl.find("float")->getType();
        }
        void print(std::ostream &stream, int ident = 0);
    };

    class SynString: public SynConst
    {
        std::string value_;
    public:
        SynString(const std::string value): value_(value) {}
        virtual SymType *getType(SymTable &tbl)
        {
            return tbl.find("char*")->getType();
        }
        void print(std::ostream &stream, int ident = 0);
    };

    class SynIndexation: public SynBinaryOp
    {
    public:
        SynIndexation(SynExpression *f, SynExpression *index): SynBinaryOp("[]", f, index) {}
        virtual SymType *getType(SymTable &tbl) { return ((SymTypePointer *)first_->getType(tbl))->getPointed();}
        virtual bool isLvalue() { return true; }
    };

    class SynSizeof: public SynExpression
    {
        SymType *type_;
    public:
        SynSizeof(SymType *type): type_(type) {}
        virtual SymType *getType(SymTable &tbl) { return tbl.find("int")->getType(); }
        void print(std::ostream &stream, int ident = 0);
    };
}

#endif // syntaxtree_h__